﻿#region License

// IOrganize.Files: a file system ETL processor
// Copyright © 2011 Business in Unison, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Configuration;

using Common.Logging;

using Patterns.Properties;
using Patterns.SafeCode;

namespace Patterns.Config
{
	/// <summary>
	/// 	Provides access to configuration via a more modern Api.
	/// </summary>
	public class ConfigFile : IConfigFile
	{
		private static int _appConfigChangeAttempts;

		private static readonly ILog _log;

		private readonly Lazy<Configuration> _appConfig =
			new Lazy<Configuration>(() => ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None));

		private readonly Configuration _manualConfig;

		private readonly Lazy<Configuration> _webConfig = new Lazy<Configuration>(OpenWebConfiguration);

		static ConfigFile()
		{
			Current = new ConfigFile();
			_log = LogManager.GetCurrentClassLogger();
		}

		private ConfigFile(Configuration config)
		{
			_manualConfig = config;
		}

		private ConfigFile()
		{
			var appSettings = GetSection<AppSettingsSection>();
			if (appSettings != null)
			{
				AppSettings = appSettings.Settings
					.OfType<KeyValueConfigurationElement>()
					.ToDictionary(e => e.Key, e => e.Value);
			}

			var connectionStrings = GetSection<ConnectionStringsSection>();
			if (connectionStrings != null)
			{
				ConnectionStrings = connectionStrings.ConnectionStrings
					.OfType<ConnectionStringSettings>()
					.ToDictionary(s => s.Name, s => s.ConnectionString);
			}
		}

		/// <summary>
		/// 	Gets the current web or application config.
		/// </summary>
		public static IConfigFile Current { get; set; }

		/// <summary>
		/// 	Gets the connection strings section as a string-to-string (name-to-connectionString) dictionary.
		/// </summary>
		public IDictionary<string, string> ConnectionStrings { get; private set; }

		/// <summary>
		/// 	Gets the app settings section as a string-to-string dictionary.
		/// </summary>
		public IDictionary<string, string> AppSettings { get; private set; }

		private Configuration Config
		{
			get
			{
				Configuration config = _manualConfig ?? _webConfig.Value ?? _appConfig.Value;
				if (config == null)
					throw new FileNotFoundException(ConfigResources.ConfigFileNotFound);
				return config;
			}
		}

		/// <summary>
		/// Loads the config file with the specified file name.
		/// </summary>
		/// <param name="fileName">Name of the file.</param>
		/// <param name="setAsCurrentConfig">if set to <c>true</c> set as the current config.</param>
		/// <returns></returns>
		public static IConfigFile Load(string fileName, bool setAsCurrentConfig = false)
		{
			var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = fileName };
			Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
			if (setAsCurrentConfig) AppDomain.CurrentDomain.SetData(ConfigResources.AppDomainConfigKey, fileName);
			if (++_appConfigChangeAttempts > 1) Try.Do(() => _log.WarnFormat(ConfigTemplates.ConfigChangeAttemptLimitExceeded, fileName));
			return new ConfigFile(config);
		}

		/// <summary>
		/// 	Gets the sections with the specified type.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <returns></returns>
		public IEnumerable<T> GetSections<T>() where T : ConfigurationSection
		{
			return Config.Sections.OfType<T>();
		}

		/// <summary>
		/// 	Gets the section with the specified type and name.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "name">The name.</param>
		/// <returns></returns>
		public T GetSection<T>(string name) where T : ConfigurationSection
		{
			return GetSections<T>().Where(section => section.SectionInformation.Name == name).FirstOrDefault();
		}

		/// <summary>
		/// 	Gets the first section in the file with the specified type.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		public T GetSection<T>() where T : ConfigurationSection
		{
			return GetSections<T>().FirstOrDefault();
		}

		private static Configuration OpenWebConfiguration()
		{
			HttpContext context = HttpContext.Current;
			if (context == null)
				return null;

			string configPath = GetWebConfigPath(context);

			return !string.IsNullOrEmpty(configPath)
			       	? WebConfigurationManager.OpenWebConfiguration(configPath)
			       	: null;
		}

		private static string GetWebConfigPath(HttpContext context)
		{
			string configPath = Settings.Default.WebConfigAppPath;
			if (string.IsNullOrEmpty(configPath)) configPath = "~/web.config";
			string path = Try.Get(() => context.Server.MapPath(configPath));
			return !string.IsNullOrEmpty(path) && File.Exists(path) ? configPath : null;
		}
	}
}